package org.icop.frame.search.virtualdoc;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.icop.frame.search.Searcher;
import org.icop.model.MultiSet;
import org.icop.model.ValuedCorrespondence;
import org.icop.utils.MathUtils;
import org.icop.virtdoc.StandardVirtualDoc;
import org.icop.virtdoc.VirtualDoc;
import org.icop.virtdoc.VirtualDocVSM;
import org.jbpt.graph.abs.IDirectedEdge;
import org.jbpt.graph.abs.IDirectedGraph;
import org.jbpt.hypergraph.abs.IVertex;

public abstract class AbstractVirtualDocSearcher<G extends IDirectedGraph<E,V>, E extends IDirectedEdge<V>, V extends IVertex> implements Searcher<G,E,V> {

	protected double cutoff;

	/**
	 * If set to true, no matches in which both related sets of nodes
	 * have a cardinality bigger than one will be returned
	 */
	protected boolean noNtoMMatches = false;

	/**
	 * If set to true, no matches in which either related sets of nodes
	 * has a cardinality bigger than one will be returned
	 */
	protected boolean no1toNMatches = false;
	
	protected boolean doWrapup = true;
	
	protected Map<G,Collection<V>> nodesInGraph;
	
	public AbstractVirtualDocSearcher(double cutoff, boolean doWrapup, boolean noNtoMMatches, boolean no1toNMatches) {
		this.cutoff = cutoff;
		this.doWrapup = doWrapup;
		this.noNtoMMatches = noNtoMMatches;
		this.no1toNMatches = no1toNMatches;
		this.nodesInGraph = new HashMap<>();
	}

	protected VirtualDoc getVirtualDocForNodes(G g, Collection<V> nodes) {
		return new StandardVirtualDoc<G,E,V>(g, nodes);
	}

	/**
	 * Creates the virtual docs for all single nodes of the graph.
	 * 
	 * @param the graph
	 * @param nodesToVirtualDoc, a map that captures the relation between nodes and virtual docs
	 * @param docs the set of virtual docs to which the created docs is added
	 */
	protected void createDocsForSingleNodes(G g, Map<Set<V>, VirtualDoc> nodesToVirtualDoc, Set<VirtualDoc> docs) {
		for (V n : this.nodesInGraph.get(g)) {
			Set<V> nodes = new HashSet<>();
			nodes.add(n);
			VirtualDoc doc = getVirtualDocForNodes(g, nodes);
			nodesToVirtualDoc.put(nodes,doc);
			docs.add(doc);
		}
	}

	/**
	 * Implements a heuristic to build docs for groups of nodes.
	 * 
	 * @param the Graph
	 * @param a map assigning node sets to docs
	 * @param the set of all docs
	 */
	protected abstract void createDocsForNodeSets(G g, Map<Set<V>, VirtualDoc> nodesToVirtualDoc, Set<VirtualDoc> docs);
		
	/**
	 * The actual search algorithm. Create docs for single nodes and groups of nodes,
	 * score them in the VSM, and select all that have a sim above the cut-off value.
	 */
	public MultiSet<ValuedCorrespondence<V>> search(G sg1, G sg2, Collection<V> nodesInSg1, Collection<V> nodesInSg2, MultiSet<ValuedCorrespondence<V>> matches) {

		this.nodesInGraph.put(sg1, nodesInSg1);
		this.nodesInGraph.put(sg2, nodesInSg2);
		
		/*
		 * Setup, in case of wrapup search
		 */
		if (doWrapup) {
			for (ValuedCorrespondence<V> c: matches){
				this.nodesInGraph.get(sg1).removeAll(c.getV1s());
				this.nodesInGraph.get(sg2).removeAll(c.getV2s());
			}
		}
		
		/*
		 * Create the virtual documents
		 */
		Set<VirtualDoc> docs = new HashSet<VirtualDoc>();
		Map<Set<V>, VirtualDoc> nodesToVirtualDoc1 = new HashMap<Set<V>, VirtualDoc>();
		Map<Set<V>, VirtualDoc> nodesToVirtualDoc2 = new HashMap<Set<V>, VirtualDoc>();
		
		/*
		 * Docs for all single nodes
		 */
		createDocsForSingleNodes(sg1,nodesToVirtualDoc1,docs);
		createDocsForSingleNodes(sg2,nodesToVirtualDoc2,docs);
		
		/*
		 * Create the virtual docs for all sets of nodes belonging to 
		 * components. These components are at max componentDepth away
		 * from the leaf nodes.
		 */
		createDocsForNodeSets(sg1, nodesToVirtualDoc1, docs);
		createDocsForNodeSets(sg2, nodesToVirtualDoc2, docs);
		
		/*
		 * Build the VSM
		 */
		VirtualDocVSM vsm = new VirtualDocVSM(docs);
		
		MultiSet<ValuedCorrespondence<V>> result = new MultiSet<>();
		/*
		 * Score all node sets and select those that have a sim
		 * above the cutoff value
		 */
		for(Set<V> nodes1 : nodesToVirtualDoc1.keySet()) {
			for(Set<V> nodes2 : nodesToVirtualDoc2.keySet()) {
				if (noNtoMMatches) 
					if (nodes1.size() > 1 && nodes2.size() > 1)
						continue;
				if (no1toNMatches) 
					if (nodes1.size() > 1 || nodes2.size() > 1)
						continue;
				
//				VirtualDoc d1 = nodesToVirtualDoc1.get(nodes1);
//				VirtualDoc d2 = nodesToVirtualDoc2.get(nodes2);
				double score = MathUtils.r(vsm.cosineForVirtualDocs(nodesToVirtualDoc1.get(nodes1), nodesToVirtualDoc2.get(nodes2))); 
				if (score > cutoff) {
					result.add(new ValuedCorrespondence<V>(nodes1,nodes2,score));
				}
			}
		}
		
		return result;
	}

}
